package com.gopay.bis.citic.fund.operation;

import java.io.*;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * 这个工具类提供了一些工具方法,并提供一些核心逻辑的方法
 * @Utils.java
 * @author dong-wang5@hnari.com
 * @2016年7月20日 上午10:03:44  www.gopay.com.cn Inc.All rights reserved.
 */
public class Utils
{
	private static Logger logger = Logger.getLogger(Utils.class);
	/**
	 * 
	 * @param message
	 * @return
	 * 针对支付宝基金报文签名
	 * 
	 * 单元测试状态:通过
	 */
	public static String cartFundSignature(String message){
		String keyFileName = Utils.getValueByName("citic.fund.cer.sign");
		File file = new File(keyFileName);
		String priKeyString = KeyIO.readKeyString(file);
		return Certificate.sign(AlgorithmEnum.RSA, priKeyString, message);
	}
	/**
	 * 
	 * @param message
	 * @param signed
	 * @return
	 * 针对支付宝基金报文验签
	 * 
	 * 单元测试状态:通过
	 */
	public static boolean cartFundVerify(String message,String signed){
		String keyFileName = Utils.getValueByName("citic.fund.cer.verify");
		File file = new File(keyFileName);
		String pubKeyString = KeyIO.readKeyString(file);
		return Certificate.verify(AlgorithmEnum.RSA, pubKeyString, message,signed);
	}
	/**
	 * 该方法用于签名
	 */
	public static InputStream signature(InputStream in)
	{
		 InputStream signIn = null;
		 DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
	     docBuilderFactory.setNamespaceAware(true);
	     DocumentBuilder docBuilder = null;
	     Document doc = null;
	     try {
	    	 byte[] content = IOUtils.toByteArray(in);
			 docBuilder = docBuilderFactory.newDocumentBuilder();
			 logger.info(new String(content,"utf-8"));
			 doc = docBuilder.parse(IOUtils.toInputStream(new String(content,"utf-8"),"utf-8"));
//			 PrivateKey priKey = KeyUtil.getMOCKPriKey();
//		     String resultXml = SignUtil.sign(doc, priKey,Utils.getElementAtrrValue(IOUtils.toInputStream(new String(content,"utf-8"),"utf-8"), "CSReq", "id"));////Utils.getElementValueFromXmlIns(IOUtils.toInputStream(new String(content,"utf-8")), "certId")
//		     signIn = IOUtils.toInputStream(resultXml, "utf-8");
		} 
	     catch (ParserConfigurationException e) 
		{
			logger.error("", e);
		} catch (SAXException e)
		{
			logger.error("", e);
		} catch (IOException e) 
		{
			logger.error("", e);
		}catch (Exception e) 
		{
			logger.error("", e);
		}
		 return signIn;
		
	}
	/**
     * 该方法是将两个数组合并成一个数组
     */
	public static byte[] uniteTwoByteArr(byte[] a,byte[] b)
	{
		if(a == null||a.length == 0)
		{
			if(b != null)
			{
				byte[] temp = new byte[b.length];
				System.arraycopy(b, 0, temp, 0, b.length);
				return temp;
			}
			else
			{
				return null;
			}
		}
		if(b == null||b.length == 0)
		{
			if(a != null)
			{
				byte[] temp = new byte[a.length];
				System.arraycopy(a, 0, temp, 0, a.length);
				return temp;
			}
			else
			{
				return null;
			}
		}
		int len = a.length + b.length;
		byte[] unite = new byte[len];
		System.arraycopy(a, 0, unite, 0, a.length);
		System.arraycopy(b, 0, unite, a.length, b.length);
		return unite;
	}
	/**
     * 该方法是将流中的内容读入一个字节数组中
     * 前20字符(全汉字40字节,320个bit位)(若是全字母数字则20字节)(汉字、字母、数字混合的由实际情况定)是特殊信息,前8个字符（16字节）是xml报文长度，
     * 前20字符后面才是真正的报文
     * 这里第二种，全是字母数字，20字符20字节
     * 返回一个二维数组，它有两个元素，1-代表前20位的特殊字符，2-代表真正的报文
     */
    public static byte[][] streamToByteArr(InputStream is)
    {
    	byte[][] returnByteArr = new byte[2][];
    	returnByteArr[0] = new byte[20];//报文特殊段
    	byte[] content = new byte[20];
    	byte[] returnBArr = null;//报文真实段
    	boolean flag = false;//标志流是否被截取，没有-false,被截取-true
    	long xmlLen = 0;//代表约定的报文长度
		int len = 0 ;	
		try 
		{
			len = is.read(content);
			while(len != -1)
			{	
				if(len == 20)
				{
					if(flag == false)
					{
						byte[] tem = new byte[8];
						System.arraycopy(content, 0, tem, 0, 8);
						xmlLen = Long.parseLong(new String(tem));
						flag = true;
						System.arraycopy(content, 0, returnByteArr[0] , 0, 20);
					}
					else
					{
						//当一个对象被奇怪的更改了，要检查有几个对象指向它，可能是因为它同时被多个引用所指向，被别的引用在什么地方操作			
						returnBArr = uniteTwoByteArr(returnBArr,content);	
					}
				}
				else
				{
					byte[] temp = new byte[len];
					System.arraycopy(content, 0, temp, 0, len);
					returnBArr = uniteTwoByteArr(returnBArr,temp);
				}	
				if(returnBArr!=null)
				{
					if(returnBArr.length>=xmlLen)
					{
						break;
					}
				}
				len = is.read(content);//程序可能阻塞在is.read(content))这里，一直读等待返回值,即直到遇见结束标志，但是此时流中若无结束标志，程序将被阻塞在这里
			}
		} catch (IOException e)
		{
			logger.error("", e);
		}
		returnByteArr[1] = returnBArr;
		return returnByteArr;

    }
	/**
	 * 此方法是通过指定的名称获取属性文件中对应的值，并返回
	 * @param name 对应文件cartoon.properties中的键值
	 */
	public static String getValueByName(String name)
	{
		InputStream is = Utils.class.getClassLoader().getResourceAsStream("properties"+ File.separator+"bis-citic.properties");
		Properties prop = new Properties();
		try 
		{
			prop.load(is);
		} catch (IOException e)
		{
			logger.error("", e);
		}
		String value = prop.getProperty(name);
		if(is != null)
		{
			try 
			{
				is.close();
			} 
			catch (IOException e) 
			{
				logger.error("", e);
			}
		}
		return value;
		
	}

	/**
	 * 将固定的域拼接到响应中
	 */
	public static StringBuffer spellRes(StringBuffer res,byte[] bytes,String[] eleNames)
	{
		for(int i = 0;i < eleNames.length;i++)
		{
			res = spellRes(res, eleNames[i],bytes);
		}
		return res;
	}
	public static StringBuffer spellRes_fund(byte[] bytes,String[] eleNames)
	{
		StringBuffer sbf = new StringBuffer();
		
		for(int i = 0;i < eleNames.length;i++)
		{
			String value= getElementValueFromXmlIns(bytesToInputStream(bytes),eleNames[i]);
			if(value == null) value="";
			if( i == 0)
				sbf.append(eleNames[i]+"=" + value);
			else
				sbf.append("&"+eleNames[i]+"=" + value);
			
		}
		
		return sbf;
	}
	
	/**
	 * 将指定的域拼接到响应中
	 */
	public static StringBuffer spellRes(StringBuffer res,String EleName,byte[] bytes)
	{
		String temp = getElementValueFromXmlIns(bytesToInputStream(bytes),EleName);
		if(temp ==  null)
		{
			temp = "";
		}
		return res.append("<"+EleName+">"+temp+"</"+EleName+">");
	}
	/**
	 * 获取联机批量标志
	 */
	public static  String getIbstrantyp(byte[] bytes)
	{
		byte[] ibstrantyp = new byte[1];
		ibstrantyp[0] = bytes[8];
		return new String(ibstrantyp);
	}
	/**
	 * 获取文件上传下载标志
	 */
	public  static String getIbsfiletyp(byte[] bytes)
	{
		byte[] ibsfiletyp = new byte[1];
		ibsfiletyp[0] = bytes[9];
		return new String(ibsfiletyp);
	}
	/**
	 * 获取交易码
	 */
	public static String getStdprocode(byte[] bytes)
	{
		byte[] stdprocode = new byte[10];
		System.arraycopy(bytes, 10, stdprocode, 0, 10);
		return new String(stdprocode);
	}
	/**
	 * 通过判断，决定是采用处理报文还是处理文件的逻辑
	 */
	public static void service(Socket s)
	{
		InputStream is = null;
		try 
		{
			is = s.getInputStream();
		} 
		
		catch (IOException e)
		{
			logger.error("", e);
		}
		byte[][] bytes = streamToByteArr(is);//获取流中的内容的字节数组,二维数组分别存放报文头和报文体
		String flag = getIbstrantyp(bytes[0]);//联机批量标志
		try 
		{
			logger.info("报文前20位:"+new String(bytes[0],"utf-8"));
			logger.info("联机批量标志:"+flag);
		} catch (UnsupportedEncodingException e1) 
		{
			e1.printStackTrace();
		}
		char FLAG = flag.charAt(0);
//		BusinessProcessor processor = new BusinessProcessor();
//		processor.process(s, bytes[0], bytes[1], FLAG);
	}
	/**
	 * 根据域名及属性名称取得对应域的对应属性值
	 */
	public static String getElementAtrrValue(InputStream is,String tagName,String attrName)
	{
		String nodeAttrValue = null;
		try {
			
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document dc = db.parse(is);//解析指定的文件
			Element root = dc.getDocumentElement();//获取根元素
			NodeList nodes = root.getElementsByTagName(tagName);
			if(nodes == null||nodes.item(0) == null || nodes.item(0).getFirstChild() == null)
			{
				return null;
			}
			Node node = nodes.item(0);
			//nodeValue = node.getFirstChild().getNodeValue();//获取该节点第一子节点的值（即是它的子文本节点）
			nodeAttrValue = node.getAttributes().getNamedItem(attrName).getNodeValue();
		} catch (DOMException e) {
			logger.error("", e);
		} catch (FactoryConfigurationError e) {
			logger.error("", e);
		} catch (ParserConfigurationException e) {
			logger.error("", e);
		} catch (SAXException e) {
			logger.error("", e);
		} catch (IOException e) {
			logger.error("", e);
		}
		return nodeAttrValue;
	}
	/**
	 * 
	 * @param pairStr
	 * @param connector
	 * @param seperator
	 * @return
	 * @throws Exception
	 * 
	 * 将键值对字符串转换为xml形式
	 * 
	 * 单元测试状态:通过
	 */
	public static String PairStr2XML(String pairStr,String connector, String seperator) throws Exception
	{
		StringBuffer result = new StringBuffer();
        if (connector == null) 
        {
            connector = "=";
        }
        if (seperator == null) 
        {
            seperator = "&";
        }
        String xmlBegin = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><ROOT>";
        result.append(xmlBegin);
        String[] pairs = pairStr.split(seperator);
        for(String pair:pairs)
        {
        	String[] keyAndValue = pair.split(connector);
        	if(pair.startsWith(connector)||"".equals(keyAndValue[0].trim()))
        	{
        		logger.error("键值对字符串的业务字段名称不能为空值,请检查报文.");
        		throw new Exception();
        	}
        	if(keyAndValue.length == 1)
        	{
        		result.append("<"+keyAndValue[0]+">"+""+"</"+keyAndValue[0]+">");
        	}
        	else
        	{
        		if(pair.endsWith(connector)){//键值对的值中出现多'='
        			if(keyAndValue.length > 2){
        				StringBuffer buf = new StringBuffer();
        				for(int i=1; i<keyAndValue.length;i++){
        					if(i == 1) buf.append(keyAndValue[i]);
        					else buf.append("="+keyAndValue[i]);
        				}
        				result.append("<"+keyAndValue[0]+">"+buf.toString()+"=</"+keyAndValue[0]+">");
        			}
        			result.append("<"+keyAndValue[0]+">"+keyAndValue[1]+"=</"+keyAndValue[0]+">");
        		}else{
        		
        				result.append("<"+keyAndValue[0]+">"+keyAndValue[1]+"</"+keyAndValue[0]+">");
        	
        			}
        		}
        	
        }
        String xmlEnd   = "</ROOT>";
        result.append(xmlEnd);
        return result.toString();
	}
	
	
	/**
	 * 
	 * @param pairStr
	 * @param connector
	 * @param seperator
	 * @return
	 * @throws Exception
	 * 
	 * 将键值对字符串转换为map形式
	 * 
	 * 单元测试状态:通过
	 */
	public static Map<String,String> PairStr2Map(String pairStr,String connector, String seperator) throws Exception
	{
        if (connector == null) 
        {
            connector = "=";
        }
        if (seperator == null) 
        {
            seperator = "&";
        }
        Map<String,String> map = new HashMap<String, String>();
        
        String[] pairs = pairStr.split(seperator);
        for(String pair:pairs)
        {
        	String[] keyAndValue = pair.split(connector);
        	if(pair.startsWith(connector)||"".equals(keyAndValue[0].trim()))
        	{
        		logger.error("键值对字符串的业务字段名称不能为空值,请检查报文.");
        		throw new Exception();
        	}
        	if(keyAndValue.length == 1)
        	{
        		map.put(keyAndValue[0], null);
        	}
        	else
        	{
        		if(pair.endsWith(connector)){//键值对的值中出现多'='
        			if(keyAndValue.length > 2){
        				StringBuffer buf = new StringBuffer();
        				for(int i=1; i<keyAndValue.length;i++){
        					if(i == 1) buf.append(keyAndValue[i]);
        					else buf.append("="+keyAndValue[i]);
        				}
        				map.put(keyAndValue[0], buf.toString());
        			}
        			map.put(keyAndValue[0], keyAndValue[1]);
        		}else{
        			
        				map.put(keyAndValue[0], keyAndValue[1]);
        			}
        		}
        	
        }
        return map;
	}
	
	
	/**
	 * 
	 * @param xmlIs	代表xml的流
	 * @param tagNames	关注的字段
	 * @param connector	连接键值的符号
	 * @param seperator	链接键值对的符号
	 * @param isAscending	是否升序排列
	 * @return	转换结果
	 * 将xml格式的报文转换成键值对互联的字符串
	 * 
	 * 单元测试状态:通过
	 */
	public static String XML2PairStr(InputStream xmlIs,String[] tagNames,String connector,
            						 String seperator,boolean isAscending)
	{
		Map<String, String> disorderMap = getElementValuesFromXmlIns(xmlIs,tagNames);
		return CipherStringBuilder.sortingMapToStr(disorderMap, connector, seperator, isAscending);
	}
	/**
	 * 根据xml报文子节流获取指定的域的原始值，即没有根据对应规则截取真实值,要求每个元素最多一个
	 * 
	 * 单元测试状态:通过
	 */
	public static Map<String, String>  getElementValuesFromXmlIns(InputStream is,String[] tagNames)
	{
		String nodeValue = null;
		Map<String, String> disorderMap = new HashMap<String, String>();
		try {
			
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document dc = db.parse(is);//解析指定的文件
			Element root = dc.getDocumentElement();//获取根元素
			for(String tagName:tagNames)
			{
				NodeList nodes = root.getElementsByTagName(tagName);
				if(nodes != null && nodes.getLength()>1)
				{
					throw new ParserConfigurationException("元素重复,检查报文.");
				}
				if(nodes == null||nodes.item(0) == null || nodes.item(0).getFirstChild() == null)
				{
					disorderMap.put(tagName, null);
				}
				else
				{
					Node node = nodes.item(0);//只处理第一个元素
					nodeValue = node.getFirstChild().getNodeValue();//获取该节点第一子节点的值（即是它的子文本节点）
					disorderMap.put(tagName, nodeValue);
				}

			}

			
		} catch (DOMException e) {
			logger.error("", e);
		} catch (FactoryConfigurationError e) {
			logger.error("", e);
		} catch (ParserConfigurationException e) {
			logger.error("", e);
		} catch (SAXException e) {
			logger.error("", e);
		} catch (IOException e) {
			logger.error("", e);
		}
		return disorderMap;
	}
	/**
	 * 根据xml报文子节流获取指定的域的原始值，即没有根据对应规则截取真实值
	 */
	public static String  getElementValueFromXmlIns(InputStream is,String tagName)
	{
		String nodeValue = null;
		try {
			
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document dc = db.parse(is);//解析指定的文件
			Element root = dc.getDocumentElement();//获取根元素
			NodeList nodes = root.getElementsByTagName(tagName);
			if(nodes == null||nodes.item(0) == null || nodes.item(0).getFirstChild() == null)
			{
				return null;
			}
			Node node = nodes.item(0);
			nodeValue = node.getFirstChild().getNodeValue();//获取该节点第一子节点的值（即是它的子文本节点）
			
		} catch (DOMException e) {
			logger.error("", e);
		} catch (FactoryConfigurationError e) {
			logger.error("", e);
		} catch (ParserConfigurationException e) {
			logger.error("", e);
		} catch (SAXException e) {
			logger.error("", e);
		} catch (IOException e) {
			logger.error("", e);
		}
		return nodeValue;
	}

	
	/**
	 * 
	 * @param pairStr
	 * @param connector
	 * @param seperator
	 * @return
	 * @throws Exception
	 * 
	 * 将键值对字符串转换为xml形式 并将service value 与交易码互转
	 * 
	 * 单元测试状态:通过
	 */
	public static String PairzfbXML(String flg ,String pairStr,String connector, String seperator) throws Exception
	{
		StringBuffer result = new StringBuffer();
        if (connector == null) 
        {
            connector = "=";
        }
        if (seperator == null) 
        {
            seperator = "&";
        }
        String xmlBegin = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>";
        result.append(xmlBegin);
        String[] pairs = pairStr.split(seperator);
        for(String pair:pairs)
        {
        	String[] keyAndValue = pair.split(connector);
        	if(pair.startsWith(connector)||"".equals(keyAndValue[0].trim()))
        	{
        		logger.error("键值对字符串的业务字段名称不能为空值,请检查报文.");
        		throw new Exception();
        	}
        	if(keyAndValue.length == 1)
        	{
        		result.append("<"+keyAndValue[0]+">"+""+"</"+keyAndValue[0]+">");
        	}
        	else
        	{
        		if(flg.equals("C") && keyAndValue[0].equals("service")){
//        			result.append("<"+keyAndValue[0]+">"+TranCodeUtil.getCode(keyAndValue[1])+"</"+keyAndValue[0]+">");
        		}else if(flg.equals("S") && keyAndValue[0].equals("service")){
//        			result.append("<"+keyAndValue[0]+">"+TranCodeUtil.getService(keyAndValue[1])+"</"+keyAndValue[0]+">");
        		} else{
	        		if(pair.endsWith(connector)){//键值对的值中出现多'='
	        			if(keyAndValue.length > 2){
	        				StringBuffer buf = new StringBuffer();
	        				for(int i=1; i<keyAndValue.length;i++){
	        					if(i == 1) buf.append(keyAndValue[i]);
	        					else buf.append("="+keyAndValue[i]);
	        				}
	        				result.append("<"+keyAndValue[0]+">"+buf.toString()+"=</"+keyAndValue[0]+">");
	        			}
	        			result.append("<"+keyAndValue[0]+">"+keyAndValue[1]+"=</"+keyAndValue[0]+">");
	        		}else{
	        				
	        				result.append("<"+keyAndValue[0]+">"+keyAndValue[1]+"</"+keyAndValue[0]+">");
	        	
	        			}
	        		}
        		}
        	
        }
        String xmlEnd   = "</root>";
        result.append(xmlEnd);
        return result.toString();
	}
	/**
	 * 实现有字节数组到输入流的转换 
	 */
	public static InputStream  bytesToInputStream(byte[] bytes)
	{
		InputStream is = null;
		if(bytes == null)
		{
			return is;
		}
		else
		{
			try 
			{
				return  IOUtils.toInputStream(new String(bytes,"utf-8"),"utf-8");
			} catch (UnsupportedEncodingException e)
			{
				logger.error("", e);
				return null;
			}
			catch (IOException e)
			{
				logger.error("", e);
				return null;
			}
		}
	}
	public static void main(String[] args) throws Exception
	{
/*		InputStream is = Utils.class.getClassLoader().getResourceAsStream("student.xml");
		byte[] bytes = IOUtils.toByteArray(is);
		InputStream iss = IOUtils.toInputStream(new String(bytes));
		String value =  getElementValueFromXmlIns(iss,"name");
		logger.info("value:"+value);*/
//		String str = Utils.XML2PairStr(Utils.class.getResourceAsStream("test.xml"), new String[]{"name","age","mm"}, true);
//		String str = Utils.PairStr2XML("_input_charset=gbk&bank_card=&partner=2088001159940003&service=purchase_single&trade_time=20100322 17:23:48", null, null);
//		logger.info(str);
		//String[] pairs = "  =aa".split("=");
		//System.out.println(pairs[0].length());
		 String message = "alipay_bank_id=95200155300000588&file_name=htf_allocation_20110606.txt" +
 		"&file_path=supervise-allocationf/20110606f_allocation_20110606.txt&file_type=ALIPAY-ALLOCATION" +
 		"&file_upload_time=20110608110109&memo=&return_url=" +
 		"&serial_no=20110608548965&service=file_upload_notify";
		 
		String signed = Utils.cartFundSignature(message);
		System.out.println("signed-->【" + signed);
		message = message+"&sign="+signed+"&sign_type=RSA";
		String signed1 ="ijz/5a/P3m0a4xnYOw9gcdHn6RFlT6InSqnyc91yNJcXcaMCmv4xxFDzxcnb4CURFuczTqgsmdhCgO7Vg5KKb+3XM9d1wF5U/xUGs8Yu1i+4hU7ZROHNtZh5tY9wYRzTviYcwSTMu62SpvZ2t3xeVTXfi9weXMC7XgpSNMAvcpY=";
		
		System.out.println(Utils.cartFundVerify(message, signed));
		//logger.info(Utils.cartFundVerify(message, signed));
	}
}
